package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.constant.EsConstant;
import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.*;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RankFeatureQueryBuilder;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.stream.Collectors;

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {

    @Autowired
    private RestHighLevelClient client;

    @Override
    public PageResult pageQueue(RequestParams params) {
        // 新建查询对象
        SearchRequest request = new SearchRequest(EsConstant.INDEX_NAME);
        // 设置查询条件，bool多条件
        extracted(params, request);


        // 设置分页条件
        int from = (params.getPage() - 1) * params.getSize();
        int size = params.getSize();
        request.source().from(from).size(size);
        // 设置根据_score排序
        request.source().sort("_score", SortOrder.DESC);
        // 设置地理位置排序
        String location = params.getLocation();
        if (location != null && !location.equals("")) {
            request.source().sort(SortBuilders
                    .geoDistanceSort("location", new GeoPoint(params.getLocation()))
                    .order(SortOrder.ASC)
                    .unit(DistanceUnit.KILOMETERS)
            );
        }
        // 结果对象
        PageResult result = new PageResult();
        try {
            // 查询
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            // 解析结果
            SearchHits hits = response.getHits();
            // 总数
            TotalHits totalHits = hits.getTotalHits();
            long total = totalHits.value;
            result.setTotal(total);
            // 获取每一条doc
            List<HotelDoc> hotels = new ArrayList<>();
            for (SearchHit hit : hits.getHits()) {
                // json
                String doc = hit.getSourceAsString();
                HotelDoc hotelDoc = JSON.parseObject(doc, HotelDoc.class);
                // json转java对象
                hotels.add(hotelDoc);
                // 解析排序值，第1个就是距离
                Object[] sortValues = hit.getSortValues();
                if (sortValues != null && sortValues.length > 1) {
                    Object distanceValue = sortValues[1];
                    hotelDoc.setDistance(Double.parseDouble(distanceValue.toString()));
                }
            }
            // 封装返回
            result.setHotels(hotels);
            return result;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public FilterVO filter(RequestParams params) throws IOException {
        SearchRequest request = new SearchRequest(EsConstant.INDEX_NAME);
        request.source().size(0);
        // 设置聚合查询
        request.source().aggregation(AggregationBuilders.terms(EsConstant.CITYAGGS).field("ctiy").size(10));
        request.source().aggregation(AggregationBuilders.terms(EsConstant.STARAGGS).field("starName").size(10));
        request.source().aggregation(AggregationBuilders.terms(EsConstant.BRANDAGGS).field("brand").size(10));
        //设置查询条件
        // 设置查询条件，bool多条件
        extracted(params, request);

        //查询
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        Aggregations aggregations = response.getAggregations();
        FilterVO result = new FilterVO();
        List<String> cityList = getStrings(aggregations, EsConstant.CITYAGGS);
        List<String> starList = getStrings(aggregations, EsConstant.STARAGGS);
        List<String> brandList = getStrings(aggregations, EsConstant.BRANDAGGS);

        result.setCity(cityList);
        result.setStarName(starList);
        result.setBrand(brandList);
        return result;
    }

    private static void extracted(RequestParams params, SearchRequest request) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 搜索关键字
        String key = params.getKey();
        if (StringUtils.isEmpty(key)) {
            // 如果用户没有输入任何东西，查询所有
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        } else {
            // 如果用户有输入，模糊匹配
            boolQueryBuilder.must(QueryBuilders.matchQuery("all", key));
        }
        // 品牌
        if (StringUtils.isNotEmpty(params.getBrand())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        // 城市
        if (StringUtils.isNotEmpty(params.getCity())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("city", params.getCity()));
        }
        //星级
        if (StringUtils.isNotEmpty(params.getStarName())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        }
        //最低价
        if (params.getMinPrice() != null) {
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()));
        }
        // 最高价
        if (params.getMaxPrice() != null) {
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lte(params.getMaxPrice()));
        }

        // 2.算分控制
        FunctionScoreQueryBuilder functionScoreQuery =
                QueryBuilders.functionScoreQuery(
                                // 原始查询，相关性算分的查询
                                boolQueryBuilder,
                                // function score的数组
                                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                                        // 其中的一个function score 元素
                                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                                // 过滤条件
                                                QueryBuilders.termQuery("isAD", true),
                                                // 算分函数
                                                ScoreFunctionBuilders.weightFactorFunction(10)
                                        )
                                })//加权模式
                        .boostMode(CombineFunction.MULTIPLY);
        request.source().query(functionScoreQuery);
    }

    private static List<String> getStrings(Aggregations aggregations, String param) {
        Terms cityAggs = aggregations.get(param);
        List<? extends Terms.Bucket> buckets = cityAggs.getBuckets();
        List<String> cityList = buckets.stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
        return cityList;
    }
}
